package com.zyl.blog.common.utils.Extreme;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.zyl.blog.common.exception.BlogException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * 加密工具类，提供安全的密码哈希功能
 *
 * <p>实现特性：
 * <ul>
 *   <li>使用SHA-256算法进行哈希计算</li>
 *   <li>自动生成随机盐值（16字节）防御彩虹表攻击</li>
 *   <li>哈希值格式：Base64(salt):Base64(hash)</li>
 *   <li>恒定时间比较防止时序攻击</li>
 * </ul>
 * @version v1.0
 * @ProjectName: Personal_Blog
 * @Package: com.zyl.common.utils.Extreme
 * @ClassName: EncryptUtil
 * @Author FinalFantasy
 * @Date 2025/2/14-16:50
 */
public final class EncryptUtil {

    private static final int SALT_LENGTH = 16;
    private static final String HASH_ALGORITHM = "SHA-256";

    /* AES密钥 (需与前端协商，示例使用128位) */
    private static final byte[] AES_KEY = "d90DjGkZD9rzbCnU".getBytes(StandardCharsets.UTF_8); // 替换为实际密钥
    private static final String AES_KEY_S = "8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c";
    private static final SecretKeySpec AES_KEY_SPEC = new SecretKeySpec(AES_KEY, "AES");

    /* IV生成器 (需确保每次加密使用唯一IV，实际应存储IV与密文一起传输) */
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    /* 预定义密钥（必须 16/24/32 字节长度，对应 AES-128/AES-192/AES-256）*/
    private static final String SECRET_KEY = "my32characteristically1234567890";

    /* 初始化 AES 实例（ECB 模式 + PKCS5Padding）*/
    private static final AES aes = SecureUtil.aes(SECRET_KEY.getBytes());

    /**
     * 生成带盐的密码哈希
     *
     * @param password 原始密码明文
     * @return 格式为"salt:hash"的字符串，使用Base64编码
     *
     * @throws IllegalArgumentException 如果password为null或空字符串
     */
    public static String hashWithSalt(String password) {
        if (password == null || password.isEmpty()) {
            throw new IllegalArgumentException("密码不能为空");
        }

        byte[] salt = generateSalt();
        byte[] hash = calculateHash(password, salt);
        return Base64.getEncoder().encodeToString(salt) +
                ":" +
                Base64.getEncoder().encodeToString(hash);
    }

    /**
     * 验证密码与存储的哈希是否匹配
     *
     * @param password    待验证的密码明文
     * @param storedHash 先前生成的存储哈希（格式：salt:hash）
     * @return true表示验证通过，false表示不匹配
     *
     * @throws BlogException 如果storedHash格式无效
     */
    public static boolean verify(String password, String storedHash) {
        if (storedHash == null || !storedHash.contains(":")) {
            throw new BlogException("无效的存储哈希格式");
        }

        String[] parts = storedHash.split(":", 2);
        byte[] salt = Base64.getDecoder().decode(parts[0]);
        byte[] expectedHash = Base64.getDecoder().decode(parts[1]);
        byte[] actualHash = calculateHash(password, salt);

        return MessageDigest.isEqual(expectedHash, actualHash);
    }

    /**
     * 生成加密安全的随机盐值
     *
     * @return 16字节的随机字节数组
     */
    private static byte[] generateSalt() {
        byte[] salt = new byte[SALT_LENGTH];
        SECURE_RANDOM.nextBytes(salt);
        return salt;
    }

    /**
     * 计算密码的哈希值（带盐）
     *
     * @param password 密码明文
     * @param salt     盐值字节数组
     * @return 哈希结果字节数组
     *
     * @throws RuntimeException 当JVM不支持SHA-256算法时抛出
     */
    private static byte[] calculateHash(String password, byte[] salt) {
        try {
            MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
            md.update(salt);
            return md.digest(password.getBytes());
        } catch (Exception ex) {
            throw new RuntimeException("加密算法不可用: " + ex.getMessage(), ex);
        }
    }


    /**
     * 加密对象为 Base64 字符串
     *
     * @param data 需要加密的对象
     * @return {@link String }
     */
    public static String encrypt(Object data) {
        try {
            // 1. 序列化为紧凑JSON（需替换实际JSON库）
            String json = JSONUtil.toJsonStr(data); // 使用hutool JSONUtil
            // 2. 压缩数据
            byte[] compressed = compress(json);

            // 3. 生成IV并加密
            byte[] iv = new byte[16];
            SECURE_RANDOM.nextBytes(iv);
            Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, AES_KEY_SPEC, new IvParameterSpec(iv));
            byte[] encrypted = cipher.doFinal(compressed);

            // 4. 组合IV+密文并进行Base64URL编码
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            output.write(iv);
            output.write(encrypted);
            return Base64.getUrlEncoder().withoutPadding().encodeToString(output.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException("Encryption failed", e); // 统一异常处理
        }
    }

    /**
     * 完整解密流程（需配合加密方法修改）
     *
     * @param encryptedData 加密后的Base64URL字符串
     * @param clazz 目标类型
     * @return 解密后的对象
     */
    public static <T> T decrypt(String encryptedData, Class<T> clazz) {
        try {
            // 1. Base64解码
            byte[] fullData = Base64.getUrlDecoder().decode(encryptedData);

            // 2. 分离IV和密文
            byte[] iv = new byte[16];
            System.arraycopy(fullData, 0, iv, 0, 16);
            byte[] ciphertext = new byte[fullData.length - 16];
            System.arraycopy(fullData, 16, ciphertext, 0, ciphertext.length);

            // 3. 解密
            Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, AES_KEY_SPEC, new IvParameterSpec(iv));
            byte[] decrypted = cipher.doFinal(ciphertext);

            // 4. 解压并反序列化
            return JSONUtil.toBean(new String(decompress(decrypted), StandardCharsets.UTF_8), clazz);
        } catch (Exception e) {
            throw new RuntimeException("Decryption failed", e);
        }
    }

    /**
     * 统一异常处理（可扩展为日志记录、监控上报等）
     */
    private static void handleEncryptException(Exception e) {
        // 1. 记录详细日志（实际代码需替换为日志框架）
        System.err.println("加密失败: " + ExceptionUtil.getMessage(e));

        // 2. 特定异常处理
        if (e != null) {
            // FastJSON序列化异常（Hutool底层可能依赖）
            System.err.println("序列化错误: 检查对象是否包含不支持的类型");
        }

        // 3. 可选：抛出自定义运行时异常
        // throw new EncryptRuntimeException("加密服务异常", e);
    }

    /**
     * 数据压缩（Deflater算法）
     *
     * @param data 待压缩数据
     * @return {@link byte[] }
     */
    private static byte[] compress(String data) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DeflaterOutputStream dos = new DeflaterOutputStream(bos, new Deflater(Deflater.BEST_COMPRESSION, true))) {
            dos.write(data.getBytes(StandardCharsets.UTF_8));
            dos.finish(); // 必须调用finish确保数据完整
            return bos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Compression failed", e);
        }
    }

    /**
     * 数据解压（与加密使用的Deflater对应）
     */
    private static byte[] decompress(byte[] compressedData) {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(compressedData);
             InflaterInputStream iis = new InflaterInputStream(bis, new Inflater(true));
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = iis.read(buffer)) > 0) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Decompression failed", e);
        }
    }

    /**
     * AES加密
     * @param content   内容
     * @return {@link String }
     */
    public static String aesEncrypt(String content) {
        return SaSecureUtil.aesEncrypt(AES_KEY_S, content);
    }

    /**
     * AES解密
     * @param content   内容
     * @return {@link String }
     */
    public static String aesDecrypt(String content) {
        return SaSecureUtil.aesDecrypt(AES_KEY_S, content);
    }
}